//
//  WDFileDownloader.m
//  Pods
//
//  Created by 刘彬 on 2017/3/22.
//
//

#import "WDFileDownloader.h"
#import "WDFileDownloadOperation.h"
#import "NSThread+Runloop.h"

NSString *const kProgressCallbackKey = @"progress";
NSString *const kCompletedCallbackKey = @"completed";

@interface WDFileDownloader () <NSURLSessionTaskDelegate, NSURLSessionDataDelegate>

/*!
 *  @brief 下载队列
 */
@property (nonatomic, strong) NSOperationQueue *downloadQueue;
/*!
 *  @brief 存储URL对应的Callback数组的字段
 */
@property (nonatomic, strong) NSMutableDictionary *URLCallbacks;
@property (nonatomic, strong) NSMutableDictionary *HTTPHeaders;
/*!
 *  @brief 操作Callback对象的一个同步队列
 */
@property (nonatomic, strong) dispatch_queue_t barrierQueue;
@property (nonatomic, strong) NSURLSession *session;

@end

@implementation WDFileDownloader

+ (WDFileDownloader *)downloader {
    static dispatch_once_t once;
    static id instance;
    dispatch_once(&once, ^{
        instance = [[self alloc] init];
    });
    return instance;
}

#pragma mark - life cycle
- (instancetype)init{
    self = [super init];
    if (self) {
        _downloadQueue = [[NSOperationQueue alloc] init];
        _downloadQueue.maxConcurrentOperationCount = 3;
        _downloadQueue.name = @"com.wanda.downloaderQueue";
        
        _URLCallbacks = [[NSMutableDictionary alloc] initWithCapacity:1];
        _HTTPHeaders = [NSMutableDictionary dictionaryWithObject:@"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8" forKey:@"Accept"];
        _barrierQueue = dispatch_queue_create("com.wanda.downloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
        _downloadTimeout = 30.0;
        
        NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
        sessionConfig.timeoutIntervalForRequest = _downloadTimeout;
        // delegateQueue设置成nil=[[NSOperationQueue alloc] init]，代理的执行工作都会异步工作
        // session本身工作的线程，与代理工作的线程是不一样的
        self.session = [NSURLSession sessionWithConfiguration:sessionConfig
                                                     delegate:self
                                                delegateQueue:nil];
    }
    return self;
}

- (void)dealloc{
    [self.session invalidateAndCancel];
    self.session = nil;
    [self.downloadQueue cancelAllOperations];
    
}

#pragma mark - download method
- (id<WDFileDownloaderCancelable>)downloadFileWithURL:(NSURL *)url
                                             progress:(WDFileDownloaderProgressBlock)progressBlock
                                            completed:(WDFileDownloaderCompletedBlock)completedBlock {
    
    __block WDFileDownloadOperation *operation;
    __weak __typeof__ (self) wself = self;
    
    dispatch_block_t createBlock = ^{
        // 设置超时时间
        NSTimeInterval timeoutInterval = wself.downloadTimeout;
        if (timeoutInterval == 0.0) {
            timeoutInterval = 30.0;
        }
        
        // request构建
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:timeoutInterval];
        request.HTTPShouldHandleCookies = YES;
        request.HTTPShouldUsePipelining = YES;
        request.allHTTPHeaderFields = wself.HTTPHeaders;
        
        // 下载过程block
        void (^progress)(NSUInteger receivedSize, NSUInteger expectedSize) = ^(NSUInteger receivedSize, NSUInteger expectedSize) {
            __strong __typeof (wself) sself = wself;
            if (!sself) return;
            __block NSArray *callbacksForURL;
            dispatch_sync(sself.barrierQueue, ^{
                callbacksForURL = [sself.URLCallbacks[url] copy];
            });
            // 回调URL对应的所有的进度block
            for (NSDictionary *callbacks in callbacksForURL) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    WDFileDownloaderProgressBlock callback = callbacks[kProgressCallbackKey];
                    if (callback) {
                        callback(receivedSize, expectedSize);
                    }
                });
            }
        };
        
        // 下载完成block
        void (^completed)(NSData *data, NSError *error, BOOL finished) = ^(NSData *data, NSError *error, BOOL finished) {
            __strong __typeof (wself) sself = wself;
            if (!sself) return;
            
            __block NSArray *callbacksForURL;
            dispatch_barrier_sync(sself.barrierQueue, ^{
                callbacksForURL = [sself.URLCallbacks[url] copy];
                if (finished) {
                    [sself.URLCallbacks removeObjectForKey:url];
                }
            });
            // 回调URL对应的所有的完成block
            for (NSDictionary *callbacks in callbacksForURL) {
                WDFileDownloaderCompletedBlock callback = callbacks[kCompletedCallbackKey];
                if (callback) {
                    callback(data, error, finished);
                }
            }
        };
        
        // 取消任务block
        void (^cancel)(void) = ^{
            __strong __typeof (wself) sself = wself;
            if (!sself) return;
            dispatch_barrier_async(self.barrierQueue, ^{
                [self.URLCallbacks removeObjectForKey:url];
            });
        };
        
        operation = [[WDFileDownloadOperation alloc] initWithRequest:request inSession:self.session progress:progress completed:completed cancelled:cancel];
        operation.continueDownloadInBackground = YES;
        operation.retryCount = self.retryCount;
        [wself.downloadQueue addOperation:operation];
    };
    
    [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:createBlock];
    
    return operation;
}

- (NSData *)downloadFileUntilFinishedWithURL:(NSURL *)url progress:(WDFileDownloaderProgressBlock)progressBlock{
    // 不需要加锁，单次修改，反复check
    __block BOOL finish = NO;
    __block NSData *result = nil;
    CFRunLoopRef runloop = CFRunLoopGetCurrent();
    CFRetain(runloop);
    
    WDFileDownloaderCompletedBlock completed = ^(NSData *data, NSError *error, BOOL finished) {
        if (finished && [data length]) {
            result = data;
        }
        finish = YES;
        // 一定要停止runloop
        CFRunLoopStop(runloop);
        CFRelease(runloop);
    };
    
    // completed必须回调，否则runloop会泄漏
    [self downloadFileWithURL:url progress:progressBlock completed:completed];
    NSTimeInterval timeoutInterval = self.downloadTimeout;
    if (timeoutInterval == 0.0) {
        timeoutInterval = 30.0;
    }
    // 阻塞当前现场直到完成或超时
    [NSThread wd_runloopBlockUntilCondition:^WDRunloopBreak{
        return finish;
    } timeOut:timeoutInterval];
    return result;
}

- (void)cancelAllDownloads {
    [self.downloadQueue cancelAllOperations];
}

#pragma mark - private
- (void)addProgressCallback:(WDFileDownloaderProgressBlock)progressBlock
             completedBlock:(WDFileDownloaderCompletedBlock)completedBlock
                     forURL:(NSURL *)url
             createCallback:(dispatch_block_t)createCallback {
    // URL为空直接回调完成
    if (url == nil) {
        if (completedBlock != nil) {
            completedBlock(nil, [NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"download URL can't be nil"}], YES);
        }
        return;
    }
    // 涉及对数据的操作，先让前面的任务执行完再添加后面的任务
    dispatch_barrier_sync(self.barrierQueue, ^{
        BOOL first = NO;
        if (!self.URLCallbacks[url]) {
            self.URLCallbacks[url] = [[NSMutableArray alloc] initWithCapacity:1];
            first = YES;
        }
        
        // 存储URL对应的callback
        NSMutableArray *callbacksForURL = self.URLCallbacks[url];
        NSMutableDictionary *callbacks = [[NSMutableDictionary alloc] initWithCapacity:2];
        if (progressBlock) {
            callbacks[kProgressCallbackKey] = [progressBlock copy];
        }
        if (completedBlock) {
            callbacks[kCompletedCallbackKey] = [completedBlock copy];
        }
        [callbacksForURL addObject:callbacks];
        self.URLCallbacks[url] = callbacksForURL;
        
        // 防止相同的URL多次发起下载请求
        if (first) {
            createCallback();
        }
    });
}

- (WDFileDownloadOperation *)operationWithTask:(NSURLSessionTask *)task {
    WDFileDownloadOperation *returnOperation = nil;
    for (WDFileDownloadOperation *operation in self.downloadQueue.operations) {
        if (operation.dataTask.taskIdentifier == task.taskIdentifier) {
            returnOperation = operation;
            break;
        }
    }
    return returnOperation;
}

#pragma mark - NSURLSessionDataDelegate
- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
 completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
    WDFileDownloadOperation *dataOperation = [self operationWithTask:dataTask];
    [dataOperation URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler];
}

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
    WDFileDownloadOperation *dataOperation = [self operationWithTask:dataTask];    
    [dataOperation URLSession:session dataTask:dataTask didReceiveData:data];
}

- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
 willCacheResponse:(NSCachedURLResponse *)proposedResponse
 completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {
    WDFileDownloadOperation *dataOperation = [self operationWithTask:dataTask];
    [dataOperation URLSession:session dataTask:dataTask willCacheResponse:proposedResponse completionHandler:completionHandler];
}

#pragma mark - NSURLSessionTaskDelegate

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    WDFileDownloadOperation *dataOperation = [self operationWithTask:task];
    [dataOperation URLSession:session task:task didCompleteWithError:error];
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
    WDFileDownloadOperation *dataOperation = [self operationWithTask:task];
    [dataOperation URLSession:session task:task didReceiveChallenge:challenge completionHandler:completionHandler];
}

#pragma mark - getter & setter
- (NSInteger)maxConcurrentDownloads {
    return _downloadQueue.maxConcurrentOperationCount;
}

- (void)setMaxConcurrentDownloads:(NSInteger)maxConcurrentDownloads {
    _downloadQueue.maxConcurrentOperationCount = maxConcurrentDownloads;
}

- (NSUInteger)currentDownloadCount {
    return _downloadQueue.operationCount;
}

- (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field {
    if (value) {
        self.HTTPHeaders[field] = value;
    } else {
        [self.HTTPHeaders removeObjectForKey:field];
    }
}

- (NSString *)valueForHTTPHeaderField:(NSString *)field {
    return self.HTTPHeaders[field];
}

- (void)setSuspended:(BOOL)suspended {
    [self.downloadQueue setSuspended:suspended];
}

@end
